/**
 * @Date: Mar 8, 2010 8:07:06 PM
 */
package com.philip.journal.home.controller.action;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;

import net.sf.json.JSONSerializer;

import com.philip.journal.common.StringUtils;
import com.philip.journal.core.Constant;
import com.philip.journal.core.bean.AbstractAuditableBean;
import com.philip.journal.core.controller.ResponseHandler;
import com.philip.journal.core.service.ServiceProxy;
import com.philip.journal.home.bean.Entry;

/**
 * @author cry30
 */
public class SearchSimpleAction extends AbstractEntryAction {

    /** Entries response attribute name. */
    private static final String ENTRIES = "entries";

    /** Total count response attribute name. */
    private static final String TOTAL_COUNT = "totalCount";

    /** Maximum length of description in the search summary list. */
    private transient int maxSummaryLen;

    /** Milliseconds in a second. */
    private static final int MIL_IN_SECS = 1000;

    /** Limit form field name passed by search. */
    private int limit;

    /** Will contain the search string. */
    private String query;

    /** This is view-required callback id for asynchronous search request. */
    private String callback;

    /** Starting index. */
    private int start;

    /** Column to sort. (title, lastUpdate) */
    private String sort;

    /** Sort direction. ASC or DESC. */
    private String dir;

    /**
     * Default delegate constructor.
     *
     * @param serviceProxy Service proxy to be injected by Spring.
     * @param responseHandler Response handler to be injected by Spring.
     */
    public SearchSimpleAction(final ServiceProxy serviceProxy, final ResponseHandler responseHandler) {
        super(serviceProxy, responseHandler);
    }

    @Override
    public void execute() throws ServletException, IOException
    {
        final String sortField = Constant.EntrySearchProperty.LAST_UPDATE.equals(getSort()) ? AbstractAuditableBean.UPDATE_DATE
                : getSort();

        final List<Entry> searchResult = getServiceProxy().searchEntriesSimple(query, sortField, getDir());
        final List<Map<String, Object>> convertedEntries = new ArrayList<Map<String, Object>>();

        final List<Entry> subList = searchResult.subList(start,
                searchResult.size() - start <= limit ? searchResult.size() : start + limit);

        final DateFormat dateF = new SimpleDateFormat("M/d/yyyy", Locale.getDefault());
        final DateFormat timeF = new SimpleDateFormat("h:m:s a", Locale.getDefault());
        final DateFormat dateTimeF = new SimpleDateFormat("M/d/yyyy h:m:s a", Locale.getDefault());

        for (final Entry next : subList) {
            final Map<String, Object> converted = new HashMap<String, Object>(); // NOPMD by r39
            converted.put(AbstractTreeAction.ENTRY_ID, next.getNodeId());
            converted.put(Constant.EntrySearchProperty.TITLE, highlightSearchMatch(next.getTitle(), query));

            final String summary = getSearchSummary(next.getDescription(), query, maxSummaryLen);
            converted.put(Constant.EntrySearchProperty.SUMMARY, summary);
            converted.put(Constant.EntrySearchProperty.ENTRY_ID, next.getNodeId());
            converted.put(Constant.EntrySearchProperty.TREEPATH, next.getTreePath());

            final Date updateDate = next.getUpdateDate();
                    final Date updateTime = next.getUpdateTime();
            long resultDate = 0;
            try {
                resultDate = dateTimeF.parse(dateF.format(updateDate) + " " + timeF.format(updateTime)).getTime()
                        / MIL_IN_SECS;
            } catch (final ParseException ignore) {
                getLogger().error(ignore);
            }

            converted.put(Constant.EntrySearchProperty.LAST_UPDATE, resultDate);
            convertedEntries.add(converted);
        }

        final Map<String, Object> respObj = new HashMap<String, Object>();
        respObj.put(ENTRIES, convertedEntries);
        respObj.put(TOTAL_COUNT, searchResult.size());

        final String responseString = JSONSerializer.toJSON(respObj).toString();
        final PrintWriter writer = getHttpServletResponse().getWriter();
        writer.print(callback + "(" + responseString + ");");
        writer.close();
    }

    /**
     * TODO: Unit Test.
     *
     * Will return summary with attempt to include the search query.
     *
     * @param desc the complete description.
     * @param pQuery the query to match.
     * @param size size
     * @return highlighted search summary.
     */
    String getSearchSummary(final String desc, final String pQuery, final int size) {
        String retval = null;
        String htmlFree = getStringUtils().removeHtmlTags(desc);

        final String lcHtmlFree = htmlFree.toLowerCase(Locale.getDefault());
        final String lcQuery = pQuery.toLowerCase(Locale.getDefault());
        final int matchIdx = lcHtmlFree.indexOf(lcQuery);
        final boolean isTitleMatch = matchIdx == -1;
        if (isTitleMatch) {
            retval = getStringUtils().substring(htmlFree, 0, size);
        } else {
            final int idx = htmlFree.substring(0, matchIdx).lastIndexOf("\n");
            if (idx > -1) {
                htmlFree = getStringUtils().substring(htmlFree, idx, idx + size);
            }

            retval = highlightSearchMatch(htmlFree, pQuery);

        }
        return retval;
    }

    /**
     * RTFC.
     *
     * @param source the text to put highlight.
     * @param pQuery the matched text to highlight.
     * @return the highlighted source.
     */
    String highlightSearchMatch(final String source, final String pQuery) {
        final StringBuffer strBuffer = new StringBuffer();
        final Pattern pattern = Pattern.compile("(?i)" + pQuery);
        final Matcher matcher = pattern.matcher(source);
        boolean result = matcher.find();
        while (result) {
            matcher.appendReplacement(strBuffer, "<font class=searchHighlight>" + matcher.group() + "</font>");
            result = matcher.find();
        }
        matcher.appendTail(strBuffer);
        return strBuffer.toString();
    }

    /**
     * @param pMaxSummaryLen the maxSummaryLen to set
     */
    public void setMaxSummaryLen(final int pMaxSummaryLen) {
        this.maxSummaryLen = pMaxSummaryLen;
    }

    StringUtils getStringUtils() {
        return StringUtils.getInstance();
    }

    public void setLimit(final int pLimit) {
        this.limit = pLimit;
    }

    public void setQuery(final String pQuery) {
        this.query = pQuery;
    }

    public void setCallback(final String pCallback) {
        this.callback = pCallback;
    }

    public void setStart(final int pStart) {
        this.start = pStart;
    }

    public int getLimit() {
        return limit;
    }

    public String getQuery() {
        return query;
    }

    public String getCallback() {
        return callback;
    }

    public int getStart() {
        return start;
    }

    /** @return {@link #sort}. */
    public String getSort() {
        return sort;
    }

    public void setSort(final String pSort) {
        this.sort = pSort;
    }

    /** @return {@link #dir}. */
    public String getDir() {
        return dir;
    }

    public void setDir(final String pDir) {
        this.dir = pDir;
    }

}
